"""Header_Information
------------------------------------
    File_Name: 数据结构和算法
    Author: RR9
    Date: 2024/09/04
    Description: 数据结构和算法使用
------------------------------------
"""
from collections import defaultdict

"""1.1 解压序列赋值到多个变量 

# 元组
tt = (1, 2)
t1, t2 = tt

# 列表
ll = [1, 2]
l1, l2 = ll

data = ['张三', 29, '贵州', (1995, 12, 21)]
name, age, city, (year, month, day) = data

# 字符串
ss = 'hello'
a, b, c, d, e = ss

"""

"""1.2 解压可迭代对象赋值给多个变量
def sum_anyargs(*args):
    print(args)
    return sum(args)


c = (1, 2, 3, 4, 5, 6)

# 打散赋值
d = sum_anyargs(*c)

"""

"""1.3 保留最后 N 个元素 
# collections.deque 是双端队列，快速添加和删除元素，可指定存放大小maxlen,超过大小自动删除最先插入元素
from collections import deque

search_num = 0


def search(lines, pattern, history=5):
    global search_num
    previous_lines = deque(maxlen=history)
    for line in lines:
        search_num += 1
        print('search_num:', search_num)
        if pattern in line:
            print('找到了，a:', search_num)
            yield line, previous_lines
            print('接着上次位置执行，当前search_num:', search_num)
        previous_lines.append(line)


def open_file(word: str):
    open_num = 1000
    with open('三字经.txt', encoding='utf-8') as f:
        for line, prevlines in search(f, word, 3):
            open_num += 1
            print('x::', open_num)
            for pline in prevlines:
                print(pline, end='')
            print(line, end='')
            print('-' * 20)


open_file('教不严')

"""

"""1.4 保留最大或最小的N 个元素
# 当查找个数为 N=1时： max(),min()
# 当查找数量N 很小时： nsmallest, nlargest
# 当查找数量N 很大时： 排序切片

from heapq import nsmallest, nlargest

# 列表、元组
nums = [1, 9, 22, 5, 87, -3.84, -32, 75, 73, 55]
tnums = tuple(nums)

print(nlargest(5, nums))
print(nsmallest(5, nums))

# 字典
portfolio = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]

print('最大3个：', nlargest(3, portfolio, key=lambda item: item['price']))
print('最小3个：', nsmallest(3, portfolio, key=lambda item: item['price']))

# 使用 max()、min()、sorted()、切片
print('使用 max()、min()、sorted()、切片 ')
print('max:', max(nums))
print('min:', min(nums))
print('max 3:', sorted(nums)[:3])
print('min 3:', sorted(nums)[-3:])
print('dict max 3:', sorted(portfolio, key=lambda item: item['price'], reverse=True)[:3])
print('dict min 3:', sorted(portfolio, key=lambda item: item['price'])[:3])

"""

"""1.5 实现一个优先级队列

import heapq


class PriorityQueue:
    '''构建一个优先队列
    使用优先级 + 索引，索引防止相同优先级情况
    相同优先级情况下，先加入先退出
    '''

    def __init__(self):
        self._queue = []
        self._index = 0

    def push(self, item, priority):
        assert priority > 0, "priority 必须大于0"
        # 以元组的形式添加，前2个参数为优先级及索引，优先级取反，保证数值越大，取反越靠前
        heapq.heappush(self._queue, (-priority, self._index, item))
        self._index += 1

    def pop(self):
        # [-1] 元组中最后的值，保证返回实际存储的值item
        return heapq.heappop(self._queue)[-1]


p = PriorityQueue()
p.push('foo', 1)
p.push('bar', 5)
p.push('spam', 4)
p.push('grok', 1)  # 与 foo 优先级相同，但foo的index 比grok小，故先出

for i in range(4):
    print(p.pop())

"""

"""1.6 字典中的键映射多个值 
'''
访问字典时，针对不存在键时的处理，默认会报错 KeyError
defaultdict 类 提供默认[] 和 (), 其中列表有序，集合去重，个人喜好
setdefault字典方法 提供自定义默认值
'''

from collections import defaultdict

d = defaultdict(list)
d['a'].append(1)
d['b'].append(2)
d['c'].append(3)
d['a'].append(99)
print(d)
# defaultdict(<class 'list'>, {'a': [1, 99], 'b': [2], 'c': [3]})

print(d['s'])  # 不存在键s, 返回[]
print('-' * 20)

d2 = defaultdict(set)
d2['a'].add(1)
d2['b'].add(2)
d2['c'].add(3)
d2['a'].add(99)

print(d2)
# defaultdict(<class 'set'>, {'a': {1, 99}, 'b': {2}, 'c': {3}})
print(d2['s'])  # 不存在键s, 返回 set()
print('-' * 20)

# 使用 setdefault字典方法，访问不存在的键可设置默认值

d3 = {}
d3.setdefault('a', []).append(1)
d3.setdefault('b', []).append(2)
d3.setdefault('c', []).append(3)
d3.setdefault('a', []).append(99)
print(d3)
# {'a': [1, 99], 'b': [2], 'c': [3]}
print(d3.setdefault('s', []))  # 不存在键s, 返回默认空列表[]
print('-' * 20)

"""

"""1.7 字典排序
'''
python3.6之后，字典默认保持插入顺序
'''
d = {}
d['a'] = 1
d['b'] = 2
d['c'] = 3
d['d'] = 4
d['e'] = 5

for key, value in d.items():
    print(key, value, end=',')  # a 1,b 2,c 3,d 4,e 5,

"""

"""1.8 字典的运算
'''
获取字典中的最大/最小值
直接使用max(dict_name) 返回的是最大的键
直接使用max(dict_name.values()) 返回的是最大的值
如何返回最值和相应的键值？
    1. 使用zip()函数：返回一个由元组组成的迭代器；其中zip是一个只能访问一次的迭代器
    2. 再比较max()/min() 比较元组，返回最值
'''

prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}

print(max(zip(prices.values(), prices.keys())))
print(min(zip(prices.values(), prices.keys())))
print(sorted(zip(prices.values(), prices.keys()))[:3])
print(sorted(zip(prices.values(), prices.keys()), reverse=True)[:3])

p = zip(prices.values(), prices.keys())
print(max(p))
# print(max(p))  # zip 只能迭代一次，多次使用报错 max() arg is an empty sequence

"""

"""1.9 查找两字典相同点
'''
键：keys() 返回一个键视图对象，支持集合操作
    键相同：a.keys() & b.keys()
    键差异：a.keys() - b.keys()

键值对：
    键值对相同：a.items() & b.items()
    键值对差异: a.items() - b.items()
    
值：values()返回一个值视图对象, 因为值不能保证互不相等，不支持集合操作，可转换为set后再操作
'''

a = {'x': 1, 'y': 2, 'z': 3}
b = {'w': 10, 'x': 11, 'y': 2}

# 比较键
print(a.keys() & b.keys())  # {'x', 'y'}
print(a.keys() - b.keys())  # {'z'}

# 比较键值对
print(a.items() & b.items())  # {('y', 2)}
print(a.items() - b.items())  # {('z', 3), ('x', 1)}

# 比较值，通常没有意义，不能保证值互不相等，且set不包含重复项
print(set(a.values()) & set(b.values()))  # {2}
print(set(a.values()) - set(b.values()))  # {1, 3}

# 排除一些键，构建新字典
c = {key: a[key] for key in a.keys() - {'y'}}
print(c)  # {'z': 3, 'x': 1}

"""

"""1.10 删除序列相同元素并保持顺序 
'''
使用生成器来构建一个新的去重列表, 其中跟sorted(),min(),max() 等相似

'''


def dedupe(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)


d1 = [1, 5, 6, 7, 5, 32, 44, 23, 34, 6]
d2 = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
print(list(dedupe(d1)))
print(list(dedupe(d2, key=lambda x: (x['x'], x['y']))))
print(list(dedupe(d2, key=lambda x: (x['x']))))
print(list(dedupe(d2, key=lambda x: (x['y']))))

"""

"""1.11 切片命名 
'''
切片命名方便代码维护
'''
n1 = 'aaaaayangsssszhiuuuude'
n2 = 'aaaaachensssszhiuuuuyu'
start = slice(5, 9)
mid = slice(13, 16)
end = slice(20, 22)

n1_name = n1[start] + n1[mid] + n1[end]
n2_name = n2[start] + n2[mid] + n2[end]

print(n1_name)
print(n2_name)

"""

"""1.12 序列中出现次数最多的元素
'''
统计次数： collections.Counter
Counter返回一个字典，键为元素，值为次数
most_common(n): 返回次数最高的前n个元素组成的元组列表： [('eyes', 8), ('the', 5), ('look', 4)]
'''
from collections import Counter

words = ['look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', 'the',
         'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the', 'eyes',
         "don't", 'look', 'around', 'the', 'eyes', 'look', 'into', 'my', 'eyes',
         "you're", 'under'
         ]

word_counts = Counter(words)
print(word_counts.most_common(3))  # 频次最高的3个

"""

""".13 通过某个关键字排序一个字典列表 
'''
方法一：itemgetter() （较lambda稍快）
方法二：lambda表达式

该方法不仅仅适用于sorted, 对max,min函数亦是如此
'''

from operator import itemgetter

rows = [
    {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
    {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
    {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
    {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]

rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))

rows_by_uidandfname = sorted(rows, key=itemgetter('uid', 'fname'))

# 使用lambda函数
rows_by_fname2 = sorted(rows, key=lambda row: row['fname'])
rows_by_uid2 = sorted(rows, key=lambda row: row['uid'])

rows_by_uidandfname2 = sorted(rows, key=lambda row: (row['uid'], row['fname']))

print(rows_by_fname)
print(rows_by_uid)
print(rows_by_uidandfname)
print(rows_by_fname2)
print(rows_by_uid2)
print(rows_by_uidandfname2)

print(max(rows, key=itemgetter('uid')))
print(min(rows, key=itemgetter('uid')))
print(max(rows, key=lambda row: row['uid']))
print(min(rows, key=lambda row: row['uid']))

"""

"""1.14 排序不支持原生比较的对象 
'''
对象的比较，通用对象属性比较
'''

class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __str__(self):
        return 'User__str__({})'.format(self.user_id)

    def __repr__(self):
        return 'User__repr__({})'.format(self.user_id)


print(User(777))
users = [User(23), User(3), User(99)]
print(sorted(users, key=lambda x: x.user_id))

"""

"""1.15 通过某个字段将记录分组
'''
列表收集分组字典，使用defaultdict,防止key不存在报错

'''

rows = [
    {'address': '5412 N CLARK', 'date': '07/01/2012'},
    {'address': '5148 N CLARK', 'date': '07/04/2012'},
    {'address': '5800 E 58TH', 'date': '07/02/2012'},
    {'address': '2122 N CLARK', 'date': '07/03/2012'},
    {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
    {'address': '1060 W ADDISON', 'date': '07/02/2012'},
    {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
    {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]

rows_by_date = defaultdict(list)
for row in rows:
    rows_by_date[row['date']].append(row)

print(rows_by_date)
print(rows_by_date['07/01/2012'])

for i in rows_by_date['07/01/2012']:
    print(i)
    
"""

"""1.16 过滤序列元素
'''
1. 列表推导：[n for n in data if n > 0]
    大数据量时，占用内存过大
2. 生成器表达式：(n for n in data if n > 0)

3. 过来非数值:
    使用filter 配合自定义函数


'''
data = [1, 4, -5, 10, -7, 2, 3, -1]

# 列表推导
d0 = [n for n in data if n > 0]
x0 = [n for n in data if n < 0]
# 针对不满足的给默认值
x02 = [n if n > 0 else 'None' for n in data]
print(d0)
print(x0)
print(x02)

# 生成器表达式
g_d0 = (n for n in data if n > 0)

for g in g_d0:
    print(g)

# 过滤非数值
values = ['1', '2', '-3', '-', '4', 'N/A', '5', 'yy', '99', 'True']


def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False


va = list(filter(is_int, values))
print('val:', va)

"""

"""1.17 从字典中提取子集
'''
1. 字典推导：
    按key提取：
    按value提取：
'''

# 按key提取
prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}

tech_names = ['ACME', 'AAPL', 'FB']
p1 = {key: value for key, value in prices.items() if key in tech_names}
# 更高效
s_p1 = {key: prices[key] for key in prices.keys() & tech_names}
print('p1:', p1)
print('s_p1:', s_p1)

# 按value提取
p2 = {key: value for key, value in prices.items() if value > 200}
print('p2:', p2)

"""

"""1.18 映射名称到序列元素
'''
命名元组 collections.namedtuple
'''
from collections import namedtuple

person = namedtuple('person', ['name', 'addr'])

p1 = person('张三', '天津')
print(p1.name, p1.addr)

"""

"""1.19 转换并同时计算数据
'''
生成器表达式
'''
nums = [1, 2, 3, 4, 5, 6]
sum1 = sum([n * n for n in nums])
# 生成器更优雅
sum2 = sum(n * n for n in nums)

portfolio = [
    {'name': 'GOOG', 'shares': 50},
    {'name': 'YHOO', 'shares': 75},
    {'name': 'AOL', 'shares': 20},
    {'name': 'SCOX', 'shares': 65}
]

max_val = max(portfolio, key=lambda n: n['shares'])
min_val = min(portfolio, key=lambda n: n['shares'])

"""

"""1.20 合并多个字典或映射
'''
collections.ChainMap:
    1. 没有创建新的字典
    2. 字典的更新或删除总是影响列表中第一个字典,更新时，没有则添加，不会影响第二个字典
'''
from collections import ChainMap

a = {'x': 1, 'z': 3}
b = {'y': 2, 'z': 4}
c = ChainMap(a, b)

c['x'] = 999  # 影响的总是第一个字典
c['y'] = 888  # 不存在是则添加，不会影响第二个字典d
print(a)
print(b)

"""